package centerberg.game;

import centerberg.engine.Engine;
import centerberg.engine.GameObject;
import centerberg.engine.SoundLoader;
import centerberg.engine.Texture;
import centerberg.engine.TextureLoader2;

import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.*;

/**
 * Loader class for reading levels and loading textures,
 * sounds and objects.
 * 
 * @author Andreas Svanberg
 */
public class LevelLoader
{
    private static final String OBJECT_PACKAGE = "centerberg.game.";
    
    private static DocumentBuilder builder = null;
    private static TextureLoader2 textureLoader = Engine.getInstance().resourceManager.textures;
    private static SoundLoader soundLoader = Engine.getInstance().resourceManager.soundLoader;
    
    private Level level = null;
    
    private LevelLoader()
    {
    }
    
    /**
     * Loads the level starting at the given root node.
     * 
     * @param root the root node containing all the data
     *             about the level
     */
    private void load(Document root)
    {
        Element rootElement = root.getDocumentElement();
        level = new Level(
                rootElement.getAttribute("name"),
                Integer.parseInt(rootElement.getAttribute("left")),
                Integer.parseInt(rootElement.getAttribute("right")));
        
        NodeList textureNodes = root.getElementsByTagName("textures");
        for (int i = 0; i < textureNodes.getLength(); i++)
        {
            loadTextures(textureNodes.item(i));
        }
        
        NodeList soundNodes = root.getElementsByTagName("sounds");
        for (int i = 0; i < soundNodes.getLength(); i++)
        {
            loadSounds(soundNodes.item(i));
        }
        
        NodeList objectNodes = root.getElementsByTagName("objects");
        for (int i = 0; i < objectNodes.getLength(); i++)
        {
            loadObjects(objectNodes.item(i));
        }
    }
    
    /**
     * Loads all the sounds in the given sounds node.
     * 
     * @param sounds node containing all sound nodes.
     */
    private void loadSounds(Node sounds)
    {
        NodeList soundList = sounds.getChildNodes();
        for (int i = 0; i < soundList.getLength(); i++)
        {
            Node sound = soundList.item(i);
            if (sound.getNodeType() == Node.ELEMENT_NODE)
            {
                loadSound(sound);
            }
        }
    }
    
    /**
     * Try to load the sound specified in the given sound node.
     * 
     * @param sound node containing the sound information
     */
    private void loadSound(Node sound)
    {
        try
        {
            String soundName = sound.getAttributes().getNamedItem("name").getTextContent();
            String soundFile = sound.getTextContent();
            
            soundLoader.addSound(soundName, soundFile);
        }
        catch (Exception e) { }
    }
    
    /**
     * Try to load the object specified in the given object node.
     * 
     * @param object node containing the object data
     */
    private void loadObject(Node object)
    {
        try
        {
            String className = OBJECT_PACKAGE + object.getNodeName();
            Class<?> objectClass = Class.forName(className);
            Method create = objectClass.getMethod("create", Map.class);
            
            Map<String, String> attributes = new HashMap<String, String>();
            NamedNodeMap attr = object.getAttributes();
            for (int i = 0; i < attr.getLength(); i++)
            {
                Node attribute = attr.item(i);
                attributes.put(attribute.getNodeName(), attribute.getTextContent());
            }
            
            GameObject obj = (GameObject)create.invoke(null, attributes);
            if (obj != null)
                level.addObject(obj);
        }
        catch (Exception e) { }
    }
    
    /**
     * Loads all the objects in the given objects node.
     * 
     * @param objects node containing object nodes
     */
    private void loadObjects(Node objects)
    {
        NodeList objectList = objects.getChildNodes();
        for (int i = 0; i < objectList.getLength(); i++)
        {
            Node object = objectList.item(i);
            if (object.getNodeType() == Node.ELEMENT_NODE)
            {
                loadObject(object);
            }
        }
    }
    
    /**
     * Loads all the textures in the given textures node.
     * 
     * @param textures node containing texture nodes
     */
    private void loadTextures(Node textures)
    {
        NodeList textureList = textures.getChildNodes();
        for (int i = 0; i < textureList.getLength(); i++)
        {
            Node texture = textureList.item(i);
            if (texture.getNodeType() == Node.ELEMENT_NODE)
            {
                loadTexture(texture);
            }
        }
    }
    
    /**
     * Try to load the texture in the given texture node.
     * 
     * @param texture node containing texture data
     */
    public void loadTexture(Node texture)
    {
        try
        {
            NamedNodeMap attributes = texture.getAttributes();

            String textureName = attributes.getNamedItem("name").getTextContent();
            String textureFile = texture.getTextContent();

            Node textureType = attributes.getNamedItem("type");
            if (textureType != null)
            {
                Texture.Type type = Texture.Type.valueOf(Texture.Type.class, textureType.getTextContent().toUpperCase());
                int width = Integer.parseInt(attributes.getNamedItem("width").getTextContent());
                int height = Integer.parseInt(attributes.getNamedItem("height").getTextContent());
                
                textureLoader.loadTexture(textureName, textureFile, type, width, height);
            }
            else
            {
                textureLoader.loadTexture(textureName, textureFile);
            }
        }
        catch (Exception e) { }
    }
    
    /**
     * Attempts to load a level from the given XML-file.
     * If the attempt should fail {@code null} is returned.
     * 
     * @param levelFile XML-file containing level data
     */
    public static Level load(String levelFile)
    {
        LevelLoader loader = new LevelLoader();
        
        try
        {
            loader.load(builder.parse(levelFile));
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        
        return loader.level;
    }
    
    /**
     * When the class is loaded attempt to create the
     * XML-parser, should it fail we exit the game since
     * it will be impossible to play anyway.
     */
    static
    {
        try
        {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setIgnoringElementContentWhitespace(true);
            factory.setIgnoringComments(true);
            builder = factory.newDocumentBuilder();
        }
        catch (Exception e)
        {
            System.exit(-2);
        }
    }
}